<?php

abstract class DBItem {
	public $id;
	public function __construct($id = null){
		$this->id = is_null($id) ? ID::GetNextFor(self::GetMetaClass()->GetDBTableName()) : ($id instanceof ID ? $id : new ID($id));
		$this->OnInit();
	}

	public function GetMenu(){ return new Menu(); }

	public static function GetClassName(){ return get_called_class(); }
	public static function GetClassTitle(){ return Lemma::Retrieve(get_called_class()); }
	public static function GetClassTitlePlural(){ return Lemma::Retrieve(get_called_class().'s'); }
	public static function GetClassTitleGeneric($classname) {
		$c = new ReflectionClass($classname);
		return $c->getMethod('GetClassTitle')->invoke(null);
	}
	public static function GetClassTitlePluralGeneric($classname) {
		$c = new ReflectionClass($classname);
		return $c->getMethod('GetClassTitlePlural')->invoke(null);
	}

	public static function GetClassIconName() { return 'oxy/ico/Icon'; }
	public static function GetClassIconNameGeneric($classname){
		$c = new ReflectionClass($classname);
		return $c->getMethod('GetClassIconName')->invoke(null);
	}
	public static function GetClassIconSrc($size=16){ return static::GetClassIconName().$size.'.gif'; }
	public static function GetClassIconSrcGeneric($classname,$size=16){ return self::GetClassIconNameGeneric($classname).$size.'.gif'; }
	public static function GetClassIcon($size=16) { return '<img src="'.static::GetClassIconSrc($size).'" width="'.$size.'" height="'.$size.'" alt="" />'; }
	public static function GetClassIconGeneric($classname,$size=16){ return '<img src="'.self::GetClassIconSrcGeneric($classname,$size).'" width="'.$size.'" height="'.$size.'" alt="" />'; }




	public function GetCode(){ return $this->id->AsHex();}
	public function GetCodeWidth(){ return '16px'; }
	public function GetTitle(){return $this->id->AsHex();}
	public final function GetTrimmedTitle($size){ $s = $this->GetTitle(); return strlen($s) > $size ? substr($s,0,$size-1).'&hellip;' : $s; }
	public function GetVersion(){return null;}
	public function GetDefaultAction(){return null;}


	public function GetIconName() { return static::GetClassIconName(); }
	protected function GetIconSrc($size=16){ return $this->GetIconName().$size.'.gif'; }
	public function GetIcon($size=16) { return '<img src="'.$this->GetIconSrc($size).'" width="'.$size.'" height="'.$size.'" alt="" />'; }
	public final function GetIconScr16(){ return $this->GetIconSrc(16); }
	public final function GetIconScr32(){ return $this->GetIconSrc(32); }
	public final function GetIconScr48(){ return $this->GetIconSrc(48); }
	public final function GetIcon16() { return $this->GetIcon(16); }
	public final function GetIcon32() { return $this->GetIcon(32); }
	public final function GetIcon48() { return $this->GetIcon(48); }


	protected function OnInit() {}




	protected function OnLoad() {}
	public final function Load(DBReader $dr = null){
		$c = self::GetMetaClass();

		//
		//
		// Load Fields
		//
		//
		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$fields = $cx->GetDBFields();
			if (is_null($dr) || $cx !== $c){
				$sql = 'SELECT id';
				foreach ($fields as $f)
					$sql .= ',' . $f->GetDBFieldName();
				$sql .= ' FROM '.$cx->GetDBTableName();
				$sql .= ' WHERE id='.new Sql($this->id);

				$dr = Database::Execute($sql);
				if (!$dr->Read()) throw new DBItemNotFoundException();
				foreach ($fields as $f){
					$n = $f->GetName();
					$this->$n = $dr[$f->GetDBFieldName()]->CastTo($f->GetType());
				}
				$dr->Close();
			}
			else {
				foreach ($fields as $f){
					$n = $f->GetName();
					$this->$n = $dr[$f->GetDBFieldName()]->CastTo($f->GetType());
				}
			}
		}


		//
		//
		// Load slaves (lazily)
		//
		//
		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$slaves = $cx->GetDBSlaves();
			foreach ($slaves as $sl) {
				$n = $sl->GetName();
				$scx = $sl->GetSlaveMetaClass();
				$sql = 'SELECT id';
				if ($sl->IsAggressive())
					for ($scxx = $scx; !is_null($scxx); $scxx = $scxx->GetParentMetaClass())
						foreach ($scxx->GetDBFields() as $f)
							$sql .= ',' . $f->GetDBFieldName();
				$sql .= ' FROM ' . $scx->GetDBTableName();
				$sql .= ' WHERE ' . $sl->GetHookDBFieldName() . '=' . new Sql($this->id) . (is_null($sl->Where) ? '' : ' AND (' . $sl->Where . ')') . (is_null($sl->OrderBy) ? '' : ' ORDER BY ' . $sl->OrderBy);
				$this->$n = new DBList($sl->GetSlaveClassName(),$sql,$sl->IsAggressive());
			}
		}

		$this->OnLoad();
	}






	protected function OnBeforeSave(){}
	protected function OnAfterSave(){}
	public function Save(){
		$this->OnBeforeSave();
		$c = self::GetMetaClass();

		//
		//
		// Save fields
		//
		//
		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$fields = $cx->GetDBFields();
			if (0==Database::ExecuteScalar('SELECT COUNT(id) FROM '.$cx->GetDBTableName().' WHERE id='.new Sql($this->id))->AsInteger()){
				$sql = 'INSERT INTO '.$cx->GetDBTableName().'(id';
				foreach ($fields as $f)
					$sql .= ','.$f->GetDBFieldName();
				$sql .= ') VALUES ('.new Sql($this->id);
				foreach ($fields as $f) {
					$n = $f->GetName();
					$sql .= ','.new Sql($this->$n);
				}
				$sql .= ')';
				Database::Execute($sql);
			}
			elseif (count($fields) > 0){
				$sql = 'UPDATE '.$cx->GetDBTableName().' SET ';
				$i = 0;
				foreach ($fields as $f) {
					$n = $f->GetName();
					if ($i++ > 0) $sql.=',';
					$sql .= $f->GetDBFieldName() . '=' . new Sql($this->$n);
				}
				$sql .= ' WHERE id='.new Sql($this->id);
				Database::Execute($sql);
			}
		}

		//
		//
		// Save slaves
		//
		//
		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$slaves = $cx->GetDBSlaves();
			foreach ($slaves as $sl) {
				$n = $sl->GetName();
				$a = $this->$n;

				// delete removed slaves
				$scx = $sl->GetSlaveMetaClass();
				$sql = 'SELECT id';
				$sql .= ' FROM ' . $scx->GetDBTableName();
				$sql .= ' WHERE ' . $sl->GetHookDBFieldName() . '=' . new Sql($this->id) . (is_null($sl->Where) ? '' : ' AND (' . $sl->Where . ')');
				$sql .= ' AND (id NOT IN '. new Sql($a).')';
				$to_be_deleted = new DBList($sl->GetSlaveClassName(),$sql);
				foreach ($to_be_deleted as $x)
					$x->Delete();

				foreach ($a as $x)
					$x->Save();
			}
		}

		$this->OnAfterSave();
	}


	protected function OnBeforeDelete(){}
	protected function OnAfterDelete(){}
	public function Delete(){
		$this->OnBeforeDelete();
		$c = self::GetMetaClass();

		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$slaves = $cx->GetDBSlaves();
			foreach ($slaves as $sl) {
				$n = $sl->GetName();
				$a = $this->$n;
				foreach ($a as $x)
					$x->Delete();
			}
		}

		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$sql = 'DELETE FROM '.$cx->GetDBTableName().' WHERE id='.new Sql($this->id);
			Database::Execute($sql);
		}

		$this->OnAfterDelete();
	}


	public static function Create(){
		$c = get_called_class();
		$r = new $c();
		self::$item_cache[get_called_class()][$r->id->AsInt()] = $r;
		return $r;
	}


	public function __clone() {
		$c = self::GetMetaClass();
		$this->id = ID::GetNextFor($c->GetRootMetaClass()->GetDBTableName());

		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()){
			$slaves = $cx->GetDBSlaves();
			foreach ($slaves as $sl) {
				$cc = $sl->GetSlaveMetaClass();
				$n = $sl->GetName();
				$a = $this->$n;
				$aa = array();
				foreach ($a as $x) {
					$xx = clone $x;
					foreach ($cc->GetDBFields() as $f){
						if ($f->GetDBFieldName() == $sl->GetHookDBFieldName()){
							$nn = $f->GetName();
							$xx->$nn = $this->id;
							break;
						}
					}
					$aa[] = $xx;
				}
				$this->$n = $aa;
			}
		}

		self::$item_cache[get_called_class()][$this->id->AsInt()] = $this;
	}





















	public static function FillMetaClass(MetaClass $c) { }
	public static final function GetMetaClass(){ return MetaClass::Retrieve(get_called_class()); }
	public static final function GetMetaSlave($hook_dbfield_name) { return new MetaSlave(get_called_class(),$hook_dbfield_name); }

	public final function GetUIObject($name=null){
		if (is_null($name)) $name = 'x'.ID::Hash($this->GetClassName().$this->id->AsHex())->AsHex();
		return new UIObject(self::GetMetaClass(),$this,$name);
	}

	public final function Read(Http $http,$name=null){
		return $this->GetUIObject($name)->Read($http);
	}


	private static $item_cache = array();
	private static $abstract_classnames = array();
	public static final function ResetCache(){ self::$item_cache = array(); }
	public static final function Retrieve($id,DBReader $dr=null){ return self::RetrieveGeneric(get_called_class(),$id,$dr); }
	public static final function RetrieveGeneric($classname,$id,DBReader $dr=null){
		if (is_null($id) || is_null($classname)) return null;
		if (!($id instanceof ID)) $id = new ID($id);
		$idi = $id->AsInt();
		if (!array_key_exists($classname,self::$item_cache)) self::$item_cache[$classname] = array();
		if (!array_key_exists($idi,self::$item_cache[$classname])){
			$c = MetaClass::Retrieve($classname);
			$tablename = $c->GetDBTableName();

			$x = null;
			if ($c->IsAbstract()){
				if (!array_key_exists($tablename,self::$abstract_classnames)) self::$abstract_classnames[$tablename] = array();
				if (!array_key_exists($idi,self::$abstract_classnames[$tablename])) {
					self::$abstract_classnames[$tablename][$idi] = Database::ExecuteScalar('SELECT '.$c->GetAbstractDBFieldName().' FROM '.$tablename.' WHERE id='.new Sql($id))->AsString();
				}
				$classname = self::$abstract_classnames[$tablename][$idi];
				$x = self::RetrieveGeneric($classname,$id);
			}
			else {
				$x = new $classname($idi);
				try {
					$x->Load($dr);
				}
				catch (DBItemNotFoundException $ex){
					return null;
				}
			}
			self::$item_cache[$classname][$idi] = $x;
		}
		return self::$item_cache[$classname][$idi];
	}




	public static final function SelectAggressively($where=null,$orderby=null){ return self::SelectGeneric(get_called_class(), $where, $orderby, true); }
	public static final function SelectAggressivelyGeneric($classname,$where=null,$orderby=null){ return self::SelectGeneric($classname, $where, $orderby, true); }
	public static final function Select($where=null,$orderby=null,$aggressively=false){ return self::SelectGeneric(get_called_class(), $where, $orderby, $aggressively); }
	public static final function SelectGeneric($classname,$where=null,$orderby=null,$aggressively=false){

		$c = MetaClass::Retrieve($classname);
		$sql = 'SELECT a.id AS id';
		if ($aggressively)
			for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass())
				foreach ($cx->GetDBFields() as $f)
					$sql .= ',' . $f->GetDBFieldName();
		$sql .= ' FROM '.$c->GetDBTableName().' AS a';

		for ($cx = $c->GetParentMetaClass(); !is_null($cx); $cx = $cx->GetParentMetaClass())
			$sql .= ','.$cx->GetDBTableName();

		for ($cx = $c->GetParentMetaClass(); !is_null($cx); $cx = $cx->GetParentMetaClass()) {
			if (!is_null($where)) $where .= ' AND ';
			$where .= $cx->GetDBTableName().'.id=a.id';
		}
		for ($cx = $c; !is_null($cx); $cx = $cx->GetParentMetaClass()) {
			if (!is_null($cx->GetOrderBy())) {
				if (!is_null($orderby)) $orderby .= ',';
				$orderby .= $cx->GetOrderBy();
			}
		}

		if (!is_null($where)) $sql .= ' WHERE '.$where;
		if (!is_null($orderby)) $sql .= ' ORDER BY '.$orderby;

		return new DBList($classname, $sql, $aggressively);
	}

	public static function SelectList(MetaField $meta_field,$where=null,$orderby=null){
		$c = $meta_field->GetMetaClass();
		$sql = 'SELECT a.'.$meta_field->GetDBFieldName().' AS id FROM '.$c->GetDBTableName().' AS a';

		for ($cx = $c->GetParentMetaClass(); !is_null($cx); $cx = $cx->GetParentMetaClass())
			$sql .= ','.$cx->GetDBTableName();

		for ($cx = $c->GetParentMetaClass(); !is_null($cx); $cx = $cx->GetParentMetaClass()) {
			if (!is_null($where)) $where .= ' AND ';
			$where .= $cx->GetDBTableName().'.id='.$c->GetDBTableName().'.id';
		}

		for ($cx = $c->GetParentMetaClass(); !is_null($cx); $cx = $cx->GetParentMetaClass()) {
			if (is_null($cx->GetOrderBy())) continue;
			if (!is_null($orderby)) $orderby .= ',';
			$orderby .= $cx->GetOrderBy();
		}

		if (!is_null($where)) $sql .= ' WHERE '.$where;
		if (!is_null($orderby)) $sql .= ' ORDER BY '.$orderby;

		return Database::ExecuteListOf($meta_field->GetType(),$sql);
	}

	public function IsEqual($dbItem) {
		if (is_null($dbItem)) return false;
		if ($dbItem instanceof DBItem) return $this->GetClassName()==$dbItem->GetClassName() && $this->id->IsEqual($dbItem->id);
		return $this->id->IsEqual($dbItem);
	}
	public static function Compare($x1,$x2){
		if (is_null($x1)) return $is_null($x2) ? 0 : -1;
		if (is_null($x2)) return 1;
		return ID::Compare($x1->id,$x2->id);
	}

	public function GetGenericID(){
		return new GenericID($this->GetClassName(),$this->id);
	}
}



?>
